#include "cbusschedule.h"
#include "assert.h"
#include "QDebug"
#include "math.h"

CBusScheduler* CBusScheduler::m_busSchedule = NULL;
CBusSchedulerXMLReader* CBusScheduler::m_xmlReader = NULL;

bool sortBusInfoLessThan(const BUS_INFO_STATION_BASED &s1, const BUS_INFO_STATION_BASED &s2)
{
    int result = QString::compare(s1.busName, s2.busName);
    if( result <= 0)
    {
        return true;
    }
    else
    {
        return false;
    }
}

void CBusScheduler::InitInstance()
{
    // if we have initialize an instance, then send out an error
    assert(m_busSchedule == NULL);

    m_busSchedule = new CBusScheduler();

    m_xmlReader = new CBusSchedulerXMLReader();
}

CBusScheduler* CBusScheduler::GetInstance()
{
    assert(m_busSchedule != NULL);

    return m_busSchedule;
}

void CBusScheduler::Destroy()
{
    assert(m_busSchedule != NULL && m_xmlReader != NULL);

    delete m_busSchedule;

    delete m_xmlReader;
}


// read bus info from xml file and generate a hash table
bool CBusScheduler::ReadBusInfoFromXML(QString filename)
{
    assert(m_xmlReader != NULL);
    bool b = m_xmlReader->LoadFile(filename.toStdString().c_str());
    if(b)
    {
        // gererate intermedia map
        m_xmlReader->GenerateIntermediaResult();
    }
    return b;
}

// according to the startPlace, endPlace and expectedTime the system will
// pick up qualified schedules that match.
void CBusScheduler::GetBusArrivingTime(QString startPlace, QString endPlace, QTime expectedTime,\
                        vector<BUS_INFO_FOR_OUT>& result)
{
    result.clear();

    BUS_INFO_FOR_OUT partialResult;
    // set the want start stationName and want get off station name
    partialResult.customerWantBeginStation = startPlace;
    partialResult.customerWantEndStation = endPlace;

    // get map info from the xml Reader
    QMultiMap<QString, BUS_INFO_STATION_BASED> stationBasedInfo = m_xmlReader->GetStationBasedResult();

    // get all the buses that pass the startPlace
    QList<BUS_INFO_STATION_BASED> listStartBusInfo = stationBasedInfo.values(startPlace);
    qSort(listStartBusInfo.begin(), listStartBusInfo.end(), sortBusInfoLessThan);

    // get all the buses that pass the endPlace
    QList<BUS_INFO_STATION_BASED> listEndBusInfo = stationBasedInfo.values(endPlace);
    qSort(listEndBusInfo.begin(), listEndBusInfo.end(), sortBusInfoLessThan);

    // find out the buses that find out the bus that will pass both startPlace and endPlace
    unsigned int j = 0;
    for(unsigned int i = 0; i < listStartBusInfo.size(); i++)
    {
        //check one by one

        for ( ; j < listEndBusInfo.size(); j++)
        {
            int compareResult = QString::compare(listStartBusInfo[i].busName, listEndBusInfo[j].busName);
            if (compareResult == 0)
            {
                // equal to each other, meaning this bus passes both the startPlace and the endPlace
                // find out all the qualified schedulers
                this->FindOutQualifiedSchedulers(&(listStartBusInfo[i]), &(listEndBusInfo[j]), expectedTime, partialResult);

                // break to the next bus
                break;
            }
            else if (compareResult < 0)
            {
                // s1 is less than s2
                break;
            }
            else if (compareResult > 0)
            {
                // s1 is greater than s2
                continue;
            }
        }

        // store the result
        if (!partialResult.busName.isEmpty())
        {
            // skip no-bus situation
            result.push_back(partialResult);
        }
        // clear partialResult
        partialResult.busName.clear();

        // we can now check next bus that passes this station
    }
}

int CBusScheduler::GetSpecifiedBusInfo(QString busName, QTime expectedTime, vector<STATION_INFO_FOR_OUT>& result)
{
    result.clear();

    // get stations info based on the busName
    m_busInfoMap = m_xmlReader->GetNameBasedResult();

    // check whether we have this bus
    if(m_busInfoMap.isEmpty() == true)
    {
        return -1; // -1 indicates that we do not have this bus
    }

    QMap<QString, vector<BUS_INFO_NAME_BASED> >::iterator it = m_busInfoMap.find(busName);

    vector<BUS_INFO_NAME_BASED> vecStationsInfoBasedOnName = it.value();

    // convert expectedTime to mins of a day
    int minsOfTheDay = (expectedTime.hour() * 60) + expectedTime.minute();

    // the following codes will try to find the bus arriving time that is equal to
    // or greater than the minsOfTheDay

    // first check whether the minsOfTheDay is valid, ie after the beginTime and before the endTime
    // only need to check the first and the last station
    int lastEvent = vecStationsInfoBasedOnName.size();
    if (   minsOfTheDay < vecStationsInfoBasedOnName[0].firstTimeStart2End
        || minsOfTheDay < vecStationsInfoBasedOnName[lastEvent - 1].firstTimeStart2End
        || minsOfTheDay > vecStationsInfoBasedOnName[0].lastTimeStart2End
        || minsOfTheDay > vecStationsInfoBasedOnName[lastEvent - 1].lastTimeEnd2Start)
    {
        // the input is invalid
        return -2; //-2 indicates that the time is not correct
    }


    // calculate the time that buses will pass the station
    for(int i = 0; i < lastEvent; i++)
    {
        // calculate the most approach time, maybe generate the result that is before the expected time
        double kStart2End = ceil((double)(minsOfTheDay - vecStationsInfoBasedOnName[i].firstTimeStart2End)/(double)vecStationsInfoBasedOnName[i].interval);
        // calc the precise time
        int deltaStart2End = vecStationsInfoBasedOnName[i].interval * kStart2End;

        // calculate the most approach time, maybe generate the result that is before the expected time
        double kEnd2Start = ceil((double)(minsOfTheDay - vecStationsInfoBasedOnName[i].firstTimeEnd2Start)/(double)vecStationsInfoBasedOnName[i].interval);
        // calc the precise time
        int deltaEnd2Start = vecStationsInfoBasedOnName[i].interval * kEnd2Start;

        STATION_INFO_FOR_OUT temp;
        temp.stationName = vecStationsInfoBasedOnName[i].stationName;
        temp.arrivingTime = vecStationsInfoBasedOnName[i].firstTimeStart2End + deltaStart2End;
        temp.arrivingTimeAnti = vecStationsInfoBasedOnName[i].firstTimeEnd2Start + deltaEnd2Start;
        result.push_back(temp);
    }

    // success
    return 0;
}


void CBusScheduler::FindOutQualifiedSchedulers(const BUS_INFO_STATION_BASED* pBeginStationInfo, \
                                               const BUS_INFO_STATION_BASED* pLastStationInfo, \
                                               const QTime expectedTime, \
                                               BUS_INFO_FOR_OUT& partialResult)
{
    int smallSeq = 0;
    int largeSeq = 0;
    bool direction = true; //true means from start to end, false is end to start

    // find the small and large seq of the bus
    smallSeq = pBeginStationInfo->seqStartToEnd >= pBeginStationInfo->seqEndToStart? \
                pBeginStationInfo->seqEndToStart : pBeginStationInfo->seqStartToEnd;

    largeSeq = pLastStationInfo->seqStartToEnd >= pLastStationInfo->seqEndToStart? \
                pLastStationInfo->seqStartToEnd : pLastStationInfo->seqEndToStart;

    // identify the direction
    // only check the pBeginBus and pLastBus's seqStartToEnd, if pBeginBus' is smaller than the pLastBus'
    // then it is from start to end, or the opposite
    direction = pBeginStationInfo->seqStartToEnd <= pLastStationInfo->seqStartToEnd? true : false;

    // convert expectedTime to mins of a day
    int minsOfTheDay = (expectedTime.hour() * 60) + expectedTime.minute();

    // calculate the most approach time, maybe generate the result that is before the expected time
    double k = 0.0;
    if (direction)
    {
        // from start to end
        k = ceil((double)(minsOfTheDay - pBeginStationInfo->firstStartTimeStartToEnd)/(double)(pBeginStationInfo->interval));
    }
    else
    {
        // from end to start
        k = ceil((double)(minsOfTheDay - pBeginStationInfo->firstStartTimeEndToStart)/(double)(pBeginStationInfo->interval));
    }

    // calc the precise time
    int delta = pBeginStationInfo->interval * k;

    // fill the result now
    partialResult.busName = pBeginStationInfo->busName;
    partialResult.directionFrom = direction? pBeginStationInfo->BeginPosition : pBeginStationInfo->EndPosition;
    partialResult.directionTo = direction? pBeginStationInfo->EndPosition : pBeginStationInfo->BeginPosition;
    partialResult.arrivingTimeForStart = direction? \
                                            pBeginStationInfo->firstStartTimeStartToEnd + delta :\
                                            pBeginStationInfo->firstStartTimeEndToStart + delta;
    partialResult.arrivingTimeForEnd = direction? \
                                            pLastStationInfo->firstStartTimeStartToEnd + delta :\
                                            pLastStationInfo->firstStartTimeEndToStart + delta;
    partialResult.howManyStationsInterval = largeSeq - smallSeq;
}

void CBusScheduler::TestBusInfoMap()
{
    // get stations info based on the busName
    QMap<QString, vector<BUS_INFO_NAME_BASED> > testMap = m_xmlReader->GetNameBasedResult();

    QMap<QString, vector<BUS_INFO_NAME_BASED> >::iterator it = testMap.begin();

    while(it != testMap.end())
    {
        qDebug() << "the bus name is " << it.key() << endl;
        vector<BUS_INFO_NAME_BASED> vecStationsInfoBasedOnName = it.value();

        // print out all the station info
        for(int i = 0; i < vecStationsInfoBasedOnName.size();i++)
        {
            /*
    static QString busName;
    static QString startStation;
    static QString endStation;
    static int interval;

    QString stationName;

    //start to end
    int firstTimeStart2End;
    int lastTimeStart2End;

    // end to start
    int firstTimeEnd2Start;
    int lastTimeEnd2Start;
            */
            qDebug() << vecStationsInfoBasedOnName[i].busName << endl;
            qDebug() << vecStationsInfoBasedOnName[i].startStation << endl;
            qDebug() << vecStationsInfoBasedOnName[i].endStation << endl;
            qDebug() << vecStationsInfoBasedOnName[i].interval << endl;
            qDebug() << vecStationsInfoBasedOnName[i].stationName << endl;
            qDebug() << vecStationsInfoBasedOnName[i].firstTimeStart2End << endl;
            qDebug() << vecStationsInfoBasedOnName[i].lastTimeStart2End << endl;
            qDebug() << vecStationsInfoBasedOnName[i].firstTimeEnd2Start << endl;
            qDebug() << vecStationsInfoBasedOnName[i].lastTimeEnd2Start << endl;

            qDebug() << endl;
        }

        it++;
    }

}

void CBusScheduler::TestGetSpecidfiedBusInfoResult(vector<STATION_INFO_FOR_OUT>& result)
{
    qDebug() << "TestGetSpecidfiedBusInfoResult result :\n";
    // just show out all the necessary info
    for (int i = 0; i < result.size();i++)
    {
        STATION_INFO_FOR_OUT out = result[i];

        qDebug() << out.stationName << " " << out.arrivingTime << " " << out.arrivingTimeAnti << endl;
    }
}

// for test purpose only
void CBusScheduler::TestGetBusArrivingTimeResult(vector<BUS_INFO_FOR_OUT>& result)
{
    qDebug() << "TestGetBusArrivingTimeResult result :\n";
    // just show out all the necessary info
    for (int i = 0; i < result.size();i++)
    {
        BUS_INFO_FOR_OUT out = result[i];

        qDebug() << out.busName << " "
                 << out.customerWantBeginStation << " "
                 << out.customerWantEndStation << " "
                 << out.directionFrom << " "
                 << out.directionTo << " "
                 << out.arrivingTimeForStart << " "
                 << out.arrivingTimeForEnd << endl;
    }
}
